﻿namespace PINGStance

module Networking = 

    open BucketFunctions

    open System
    open System.Text
    open System.Threading

    open System.Net
    open System.Net.NetworkInformation


    //
    let GetIPAddress( host_name : string ) =

        let ips =  System.Net.Dns.GetHostAddresses( host_name )

        ips.[0]

    //  
    let PingIPAddress(  ip_address : System.Net.IPAddress,
                        timeout_ms : int,
                        buffer : byte[],
                        ttl : int,
                        dont_fragment: bool ) : System.Net.NetworkInformation.PingReply = 
            
        let ping_options = new System.Net.NetworkInformation.PingOptions( ttl, dont_fragment )
        let ping_sender  = new NetworkInformation.Ping()
        let ping_reply = ping_sender.Send(  ip_address,
                                            timeout_ms,
                                            buffer,
                                            ping_options
                                            )

        ping_sender.Dispose()
        
        ping_reply

    //
    let PingTest( ipaddress : string, 
                  ping_buffer_size : uint16, 
                  report_at_ms : uint16, 
                  ping_rate_sec : uint16, 
                  log_to_console,
                  lock : System.Threading.ReaderWriterLock ) = 

        //  Create a buffer for the ping
        let ping_buffer = [| for i in 1 .. int ping_buffer_size -> byte 'a' |]

        //  Find IP address of the target
        let target_ip_address = GetIPAddress( ipaddress )
        
        //  Ping the target to see if it's pingable
        let mutable ping_reply = PingIPAddress( target_ip_address, 
                                                1000, 
                                                ping_buffer,
                                                128,
                                                false
                                                )

        let mutable msg = ("DestIP | " + target_ip_address.ToString() + " | " + ( if ping_reply.Status = IPStatus.Success then "Ping OK" else "Ping BAD" ))
        write_log_file( "NB", msg, true, log_to_console, lock )

        //  Use Stopwatch to measure the ping times rather than roundtriptime
        let stopWatch = new System.Diagnostics.Stopwatch()
        
        let mutable ttl = 1
 
        while 1 = 1 do          //  Do forever
            ttl <- 1

            //  Ping through the TTL of 1 then 2, then 3 etc. - until reaching the target IP address (target_ip_address)

            while ttl > 0 do
                stopWatch.Reset()
                stopWatch.Start()

                ping_reply <- PingIPAddress(    target_ip_address, 
                                                1000, 
                                                ping_buffer,
                                                ttl,
                                                false
                                                )
                stopWatch.Stop()

                if not ( ping_reply.Status = IPStatus.TtlExpired || ping_reply.Status = IPStatus.TimedOut || ping_reply.Status = IPStatus.Success ) then
                    // Failure
                    msg <- ("FAILURE ttl " + ttl.ToString() + " | " + ping_reply.Address.ToString() + " | dur_ms " + stopWatch.ElapsedMilliseconds.ToString())
                    write_log_file( "NF", msg, true, log_to_console, lock )

                else 
                    if ( stopWatch.ElapsedMilliseconds >= int64 report_at_ms || report_at_ms = uint16 0 )
                         && ping_reply.Status <> IPStatus.TimedOut then
                        //  Report if the ping duration is >= the report threshold then log it
                        if ping_reply.Status = IPStatus.TtlExpired || ping_reply.Status = IPStatus.Success then  
                            msg <- "ttl " + ttl.ToString() + " | " + ping_reply.Address.ToString() + " | dur_ms " + stopWatch.ElapsedMilliseconds.ToString()
                            write_log_file( "NS", msg, true, log_to_console, lock )
                
                    if ping_reply.Status = IPStatus.TtlExpired || ping_reply.Status = IPStatus.TimedOut then 
                        ttl <- ttl + 1      //  Still routing, not at the destination yet so go to next hop
                
                    else 
                        ttl <- 0            //  Break out - destination reached.

                    Thread.Sleep( 100 )     //  Wait between successive pings across the route

            Thread.Sleep( int ping_rate_sec * 1000 )

        0       //  Return Code

